package viewer;

import javax.swing.*;
import javax.swing.event.*;

import model.RoffVector3D;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;

public class RoffSpherePanel extends JPanel {

    private int squareSize;

    private int upperLeftX;
    private int upperLeftY;
    private Dimension currentSize;

    private BufferedImage       buffer;
    private RoffPerspective     transformer;
    private RoffPhong           illuminator;

    public RoffSpherePanel(RoffPerspective newPerspective, RoffPhong newPhong) {

        transformer = newPerspective;
        illuminator = newPhong;
        squareSize    = 150;

        currentSize = getSize();

        buffer = new BufferedImage(squareSize, squareSize, BufferedImage.TYPE_INT_ARGB);
        this.setPreferredSize(new Dimension(150, 150));
        this.setBackground(Color.black);
    }

    public void fullPaint() {
        buildBuffer();
        repaint();
    }


    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Dimension testSize = getSize();
        if (!currentSize.equals(testSize)) {
            if (testSize.width > testSize.height) {
                upperLeftY = 0;
                upperLeftX = (testSize.width / 2) - (testSize.height / 2);
                squareSize = testSize.height;
            } else {
                upperLeftX = 0;
                upperLeftY = (testSize.height / 2) - (testSize.width / 2);
                squareSize = testSize.width;
            }
            currentSize = testSize;
            buffer = new BufferedImage(squareSize, squareSize, BufferedImage.TYPE_INT_ARGB);
            buildBuffer();
        }
        g.drawImage(buffer, upperLeftX, upperLeftY, null);
    }

    private void buildBuffer() {

        final WritableRaster raster = buffer.getRaster();
        final ColorModel model = buffer.getColorModel();
        final Object blackData = model.getDataElements((Color.black).getRGB(), null);
        Object colorData;

        final int halfSize = squareSize / 2;
        final int shortSquareSize = squareSize - 1;

        illuminator.setWorldLight(transformer.EyeToWorld(illuminator.getLightDirection()));

        // Variables for temporary storage during processing.
        double u;
        double v;
        double a;
        double t1;
        double t2;
        double det;
        double root;
        final RoffVector3D t1Point = new RoffVector3D();
        final RoffVector3D t2Point = new RoffVector3D();
        RoffVector3D view;

        final Graphics2D g = buffer.createGraphics();

        // Fill the background with the background color
        g.setPaint(Color.black);
        g.fill(new Rectangle(0, 0, squareSize, squareSize));

        g.setPaint(illuminator.getOutsideColor());	

        for (int j = 0; j < squareSize; j++) {
            for (int i = 0; i < squareSize; i++) {
                // Find normalized double values, centered at 0,0, for i,j.
                u = (i - halfSize) / (float)squareSize;
                v = (j - halfSize) / (float)squareSize;
                a = (u * u + v * v + 1.0);
                // det = b^2 - 4 * a * c
                det = 25.0 - 21.0 * a;
                // If there are roots, then each intersection,
                // might be where the ray hits the sphere.
                if (det > 0) {
                    // Find both intersections.
                    root = Math.sqrt(det);
                    t1 = (5.0 + root) / (2.0 * a);
                    t2 = (5.0 - root) / (2.0 * a);
                    t1Point.x = -u * t1;
                    t1Point.y = v * t1;
                    t1Point.z = t1 - 2.5;
                    t2Point.x = -u * t2;
                    t2Point.y = v * t2;
                    t2Point.z = t2 - 2.5;
                    // Note: Because t1Point and t2Point are
                    // points on a unit sphere, their lengths are
                    // both 1, and they are the normal of the sphere
                    // at their respective points.
                    // Select the lower z of the two, which is the closer point.
                    if (t1Point.z <= 0) {
                        view = (transformer.getFrom()).sub(t1Point);
                        view.normalize();
                        if (t1Point.dot(view) > 0) {
                            colorData = model.getDataElements(illuminator.illuminate(t1Point, view).getRGB(), null);
                        } else {
                            colorData = blackData;
                        }
                    } else if (t2Point.z <= 0) {
                        view = (transformer.getFrom()).sub(t2Point);
                        view.normalize();
                        if (t2Point.dot(view) > 0) {
                            colorData = model.getDataElements(illuminator.illuminate(t2Point, view).getRGB(), null);
                        } else {
                            colorData = blackData;
                        }
                    } else {
                        colorData = blackData;
                    }
                } else {
                    colorData = blackData;
                }
                raster.setDataElements(i, shortSquareSize - j, colorData);
            }
        }
    }
}